#include "ObjectFactory.h"
//#include "AbstractObject.h"
#include "Sheet.h"
#include "Logger.h"
#include "Filesystem.h"
#include "LuaUtils.h"

SingletonCppProtoDepot* SingletonCppProtoDepot::_theDepot = NULL;

ObjectFactory::ObjectFactory(Sheet* sh)
{
	LOG(logDEBUG) << "ObjectFactory initializing for sheet " << sh->getId();

	_sh = sh;

	// Initialize all pure cpp prototypes
	SingletonCppProtoDepot* depot = SingletonCppProtoDepot::get();

	for(int i = 0; i < depot->getNumProto(); ++i)
	{
		AbstractObject* curProto = depot->getProto(i);
		addCppProto(curProto, curProto->getCppTypeName());
	}

	registerCustomDatatypes();

	LOG(logDEBUG) << "OF done init.";
}

void ObjectFactory::addCppProto(AbstractObject* cppProto, std::string name)
{
	LOG(logDEBUG) << "OF adding CppProto : " << name;
	ObjectProto* newProto = new ObjectProto;
	newProto->finalized = true;
	newProto->isPureCpp = true;
	newProto->name = name;
	newProto->parentName = "AbstractObject";
	newProto->cppObj = cppProto;
	newProto->luaRef = 0;
	_protos[name] = newProto;

	lua_State* L = _sh->getL();
	// fill a table with the static functions
	lua_newtable(L);
	cppProto->pushStaticFuncs(L);
	int regIndex = luaL_ref(L, LUA_REGISTRYINDEX);
	newProto->luaRef = regIndex;

	// Call core.produceCppPrototype(staticTable, className)
	_sh->getLuaCoreFunc("produceCppPrototype", L);
	lua_rawgeti(L, LUA_REGISTRYINDEX, regIndex);
	lua_pushstring(L, name.c_str());
	if(lua_pcall(L, 2, 0, 0) != 0) // error!
	{
		LOG(logERROR) << "Cpp prototype production failed: " << std::string(getSafeLuaString(L, -1));
		lua_settop(L, -2);
	}
}

AnyStrongAOPtr ObjectFactory::createObject(std::string className)
{
	ObjectProto* theProto = getPrototype(className);
	AnyStrongAOPtr ret(NULL);
	if(!theProto)
	{
		LOG(logERROR) << "Class " << className << " does not exist.";
		return ret;
	}

	if(!(theProto->cppObj))
	{
		LOG(logERROR) << "Class " << className << " has no c++ component!";
		return ret;
	}

	if(!(theProto->finalized))
	{
		LOG(logERROR) << "Class " << className << " has not been finalized yet!";
		return ret;
	}

	AbstractObject* cppPart = theProto->cppObj->newInstance();
	ret = cppPart;
	cppPart->setSheet(_sh);
	int luaPart = instantiateLuaPart(theProto->luaRef, cppPart);
	cppPart->setLuaRef(luaPart);
	cppPart->setLuaTypeName(className);

	return ret;
}

// note that this only works before a prototype is finalized
void ObjectFactory::setInheritance(std::string className, std::string parentName)
{
	if(!loadPrototype(parentName,false))
	{
		LOG(logERROR) << "Cannot set inheritance " << className << " <- " << parentName << " because parent does not exist.";
		return;
	}

	if(_protos.count(className) == 0)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << " because it does not exist.";
		return;
	}

	ObjectProto* child = _protos[className];
	ObjectProto* parent = _protos[parentName];

	if(child->finalized)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << "; class already finalized.";
		return;
	}
	
	if(!(parent->finalized))
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << "; parent " << parentName << " not finalized.";
		return;
	}

	if(child->isPureCpp)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << "; class is C++ class";
		return;
	}
	if(child->parentName != "")
		LOG(logWARNING) << "Warning: overriding inheritance of " << className << " from " << child->parentName << " to " << parentName;

	child->parentName = parentName;
	child->cppObj = parent->cppObj;

	lua_State* L = _sh->getL();

	// set child's luaRef's metatable to parent's luaRef
	// get child then parent to have a stack of:
	// parent -1
	// child -2
	lua_rawgeti(L, LUA_REGISTRYINDEX, child->luaRef);
	lua_rawgeti(L, LUA_REGISTRYINDEX, parent->luaRef);
	// set metatable (pops parent) to leave:
	// child -1
	lua_setmetatable(L, -2);
	// clear stack
	lua_settop(L, -2);

}

void ObjectFactory::setCppInheritanceOnly(std::string className, std::string parentName)
{
	if(!loadPrototype(parentName,false))
	{
		LOG(logERROR) << "Cannot set inheritance " << className << " <- " << parentName << " because parent does not exist.";
		return;
	}

	if(_protos.count(className) == 0)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << " because it does not exist.";
		return;
	}

	ObjectProto* child = _protos[className];
	ObjectProto* parent = _protos[parentName];

	if(child->finalized)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << "; class already finalized.";
		return;
	}
	if(child->isPureCpp)
	{
		LOG(logERROR) << "Cannot set inheritance for " << className << "; class is C++ class";
		return;
	}
	if(child->parentName != "")
		LOG(logWARNING) << "Warning: overriding inheritance of " << className << " from " << child->parentName << " to " << parentName;

	child->parentName = parentName;
	child->cppObj = parent->cppObj;
}

ObjectProto* ObjectFactory::getPrototype(std::string className)
{
	if(loadPrototype(className, false))
		return _protos[className];
	else
	{
		LOG(logERROR) << "Unable to find a prototype for " << className;
		return NULL;
	}
}

void splitName(std::string className, std::string& leftPart, std::string& rightPart)
{
	// search for first colon
	int colonPos = className.find_first_of(':');
	if(colonPos >= 0 && (unsigned int)colonPos < className.size() - 1) // found a colon (ignore case where colon is last character)
	{
		leftPart = className.substr(0, colonPos);
		rightPart = className.substr(colonPos + 1);
	}
	else
	{
		leftPart = className;
		rightPart = "";
	}
}

// this doesn't ever have to be explicitely called, since getPrototype
//  and createObject will automatically call it as necessary
bool ObjectFactory::loadPrototype(std::string className, bool reload)
{
	if(className == "")
	{
		LOG(logWARNING) << "Tried to load null (empty string) class";
		return true;
	}

	if(!reload && _protos.count(className) > 0)
		return true;
	else if(reload && _protos.count(className) > 0) // try to reload
	{
		LOG(logERROR) << "Prototype reloading not implemented yet. Cannot reload " << className;
		return false;
	}
	else
	{
		std::string leftPart = "";
		std::string rightPart = "";
		splitName(className, leftPart, rightPart);
		if(leftPart == "")
		{
			LOG(logERROR) << "Malformed class name " << className;
			return false;
		}
		// Load in left part

		ObjectProto* newProto = new ObjectProto;
		newProto->finalized = false;
		newProto->isPureCpp = false;
		newProto->name = className;
		newProto->parentName = "";
		newProto->cppObj = NULL;
		newProto->luaRef = 0;
		_protos[className] = newProto;

		lua_State* L = _sh->getL();
		Filesystem* fs = _sh->fs();
		// Load in script file as a chunk
		int lcres = fs->loadLuaChunk(L, leftPart, className);
		if(lcres < 0)
		{
			LOG(logERROR) << "Could not load chunk for " << leftPart;
			delete newProto;
			_protos.erase(className);
			return false;
		}
		// Create an empty table, put it in the registry, set newProto->luaRef to that value
		lua_newtable(L);
		int regIndex = luaL_ref(L, LUA_REGISTRYINDEX);
		newProto->luaRef = regIndex;
		// Call core.producePrototype(protoChunk, newTable, className, parentName)
		_sh->getLuaCoreFunc("producePrototype", L);
		lua_insert(L, -2);
		lua_rawgeti(L, LUA_REGISTRYINDEX, regIndex);
		lua_pushstring(L, className.c_str());
		lua_pushstring(L, rightPart.c_str());
		if(lua_pcall(L, 4, 0, 0) != 0) // error!
		{
			LOG(logERROR) << "Prototype production failed: " << std::string(getSafeLuaString(L, -1));
			lua_settop(L, -2);
		}

		// Inherit from right part if it exists
		//  (note that inherit will load right part if it isn't loaded)
		//  (this should be done by the lua core function)
		/*
		if(rightPart != "")
			setInheritance(className, rightPart);
		*/

		// If somehow we have managed to avoid inheriting, inherit from AbstractObject
		if(newProto->parentName == "" || newProto->cppObj == NULL)
		{
			LOG(logWARNING) << "No parent for " << className << "; defaulting to AbstractObject";
			setInheritance(className, "AbstractObject");
		}

		// 'Finalize' this prototype
		newProto->finalized = true;

		return true;
	}
}

void ObjectFactory::registerCustomDatatypes()
{
	SingletonCppProtoDepot* depot = SingletonCppProtoDepot::get();
	LOG(logDEBUG) << "Found " << depot->getNumDatatypes() << " custom datatypes.";

	lua_State* L = _sh->getL();
	for(int i = 0; i < depot->getNumDatatypes(); ++i)
	{
		DataTypeStub tdt = depot->getDatatype(i);
		LOG(logDEBUG2) << "Registering custom datatype " << tdt.name;

		luaL_newmetatable(L, tdt.name.c_str());

		lua_pushstring(L, "__index");
		lua_pushvalue(L, -2);  /* pushes the metatable */
		lua_settable(L, -3);  /* metatable.__index = metatable */

		tdt.dt->registerFunctions(L);
		lua_settop(L, -2); // pop the table we made
	}
}

int ObjectFactory::instantiateLuaPart(int luaProto, AbstractObject* cppPart)
{
	lua_State* L = _sh->getL();
	// first, get core.makeInstanceTable
	_sh->getLuaCoreFunc("makeInstanceTable", L);
	if(lua_pcall(L, 0, 1, 0) != 0) // error!
	{
		LOG(logERROR) << "Instance table production failed: " << std::string(getSafeLuaString(L, -1));
		lua_settop(L, -2);
		return -1;
	}

	// next, push a AOPtr as a user data as __sp
	AnyAOPtr bla(cppPart);
	bla.push_AOPtr(L);
	lua_setfield(L, -2, "__sp");

	// next, set the table's metatable to the luaProto (which must be fetched from the registry)
	lua_rawgeti(L, LUA_REGISTRYINDEX, luaProto);
	lua_setmetatable(L, -2);

	// next, add this table to the registry
	// return the index
	return luaL_ref(L, LUA_REGISTRYINDEX);
}
